package cmd

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"github.com/louloulin/dataflare/pkg/client/go"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v3"
)

var (
	workflowName        string
	workflowDescription string
	workflowFile        string
	workflowID          string
	runID               string
	parameters          string
	limit               int
	nextToken           string
	wait                bool
	timeout             int
	pollInterval        int
)

// workflowCmd represents the workflow command
var workflowCmd = &cobra.Command{
	Use:   "workflow",
	Short: "Manage workflows",
	Long:  `Manage DataFlare workflows.`,
}

// workflowListCmd represents the workflow list command
var workflowListCmd = &cobra.Command{
	Use:   "list",
	Short: "List workflows",
	Long:  `List DataFlare workflows.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Create client
		c := client.NewClient(
			apiURL,
			client.WithAPIKey(apiKey),
			client.WithTimeout(30*time.Second),
		)

		// List workflows
		response, err := c.ListWorkflows(context.Background(), nextToken, limit)
		if err != nil {
			return err
		}

		// Output
		switch outputFormat {
		case "json":
			json, err := json.MarshalIndent(response, "", "  ")
			if err != nil {
				return err
			}
			fmt.Println(string(json))
		case "yaml":
			yaml, err := yaml.Marshal(response)
			if err != nil {
				return err
			}
			fmt.Println(string(yaml))
		default:
			fmt.Println("WORKFLOWS")
			fmt.Println("ID\tNAME\tDESCRIPTION\tVERSION\tSTATUS\tCREATED AT")
			for _, workflow := range response.Workflows {
				fmt.Printf("%s\t%s\t%s\t%s\t%s\t%s\n",
					workflow.ID,
					workflow.Name,
					workflow.Description,
					workflow.Version,
					workflow.Status,
					workflow.CreatedAt.Format(time.RFC3339),
				)
			}
			if response.NextToken != "" {
				fmt.Printf("\nNext token: %s\n", response.NextToken)
			}
		}

		return nil
	},
}

// workflowCreateCmd represents the workflow create command
var workflowCreateCmd = &cobra.Command{
	Use:   "create",
	Short: "Create a workflow",
	Long:  `Create a DataFlare workflow.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Read workflow file
		definition, err := ioutil.ReadFile(workflowFile)
		if err != nil {
			return err
		}

		// Create client
		c := client.NewClient(
			apiURL,
			client.WithAPIKey(apiKey),
			client.WithTimeout(30*time.Second),
		)

		// Create workflow
		workflow, err := c.CreateWorkflow(context.Background(), &client.CreateWorkflowRequest{
			Name:        workflowName,
			Description: workflowDescription,
			Definition:  string(definition),
		})
		if err != nil {
			return err
		}

		// Output
		switch outputFormat {
		case "json":
			json, err := json.MarshalIndent(workflow, "", "  ")
			if err != nil {
				return err
			}
			fmt.Println(string(json))
		case "yaml":
			yaml, err := yaml.Marshal(workflow)
			if err != nil {
				return err
			}
			fmt.Println(string(yaml))
		default:
			fmt.Println("WORKFLOW")
			fmt.Printf("ID: %s\n", workflow.ID)
			fmt.Printf("Name: %s\n", workflow.Name)
			fmt.Printf("Description: %s\n", workflow.Description)
			fmt.Printf("Version: %s\n", workflow.Version)
			fmt.Printf("Status: %s\n", workflow.Status)
			fmt.Printf("Created at: %s\n", workflow.CreatedAt.Format(time.RFC3339))
			fmt.Printf("Updated at: %s\n", workflow.UpdatedAt.Format(time.RFC3339))
		}

		return nil
	},
}

// workflowGetCmd represents the workflow get command
var workflowGetCmd = &cobra.Command{
	Use:   "get",
	Short: "Get a workflow",
	Long:  `Get a DataFlare workflow.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Create client
		c := client.NewClient(
			apiURL,
			client.WithAPIKey(apiKey),
			client.WithTimeout(30*time.Second),
		)

		// Get workflow
		workflow, err := c.GetWorkflow(context.Background(), workflowID)
		if err != nil {
			return err
		}

		// Output
		switch outputFormat {
		case "json":
			json, err := json.MarshalIndent(workflow, "", "  ")
			if err != nil {
				return err
			}
			fmt.Println(string(json))
		case "yaml":
			yaml, err := yaml.Marshal(workflow)
			if err != nil {
				return err
			}
			fmt.Println(string(yaml))
		default:
			fmt.Println("WORKFLOW")
			fmt.Printf("ID: %s\n", workflow.ID)
			fmt.Printf("Name: %s\n", workflow.Name)
			fmt.Printf("Description: %s\n", workflow.Description)
			fmt.Printf("Version: %s\n", workflow.Version)
			fmt.Printf("Status: %s\n", workflow.Status)
			fmt.Printf("Created at: %s\n", workflow.CreatedAt.Format(time.RFC3339))
			fmt.Printf("Updated at: %s\n", workflow.UpdatedAt.Format(time.RFC3339))
		}

		return nil
	},
}

// workflowUpdateCmd represents the workflow update command
var workflowUpdateCmd = &cobra.Command{
	Use:   "update",
	Short: "Update a workflow",
	Long:  `Update a DataFlare workflow.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Create client
		c := client.NewClient(
			apiURL,
			client.WithAPIKey(apiKey),
			client.WithTimeout(30*time.Second),
		)

		// Create request
		request := &client.UpdateWorkflowRequest{}
		if workflowDescription != "" {
			request.Description = workflowDescription
		}
		if workflowFile != "" {
			// Read workflow file
			definition, err := ioutil.ReadFile(workflowFile)
			if err != nil {
				return err
			}
			request.Definition = string(definition)
		}

		// Update workflow
		workflow, err := c.UpdateWorkflow(context.Background(), workflowID, request)
		if err != nil {
			return err
		}

		// Output
		switch outputFormat {
		case "json":
			json, err := json.MarshalIndent(workflow, "", "  ")
			if err != nil {
				return err
			}
			fmt.Println(string(json))
		case "yaml":
			yaml, err := yaml.Marshal(workflow)
			if err != nil {
				return err
			}
			fmt.Println(string(yaml))
		default:
			fmt.Println("WORKFLOW")
			fmt.Printf("ID: %s\n", workflow.ID)
			fmt.Printf("Name: %s\n", workflow.Name)
			fmt.Printf("Description: %s\n", workflow.Description)
			fmt.Printf("Version: %s\n", workflow.Version)
			fmt.Printf("Status: %s\n", workflow.Status)
			fmt.Printf("Created at: %s\n", workflow.CreatedAt.Format(time.RFC3339))
			fmt.Printf("Updated at: %s\n", workflow.UpdatedAt.Format(time.RFC3339))
		}

		return nil
	},
}

// workflowDeleteCmd represents the workflow delete command
var workflowDeleteCmd = &cobra.Command{
	Use:   "delete",
	Short: "Delete a workflow",
	Long:  `Delete a DataFlare workflow.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Create client
		c := client.NewClient(
			apiURL,
			client.WithAPIKey(apiKey),
			client.WithTimeout(30*time.Second),
		)

		// Delete workflow
		err := c.DeleteWorkflow(context.Background(), workflowID)
		if err != nil {
			return err
		}

		fmt.Printf("Workflow %s deleted\n", workflowID)

		return nil
	},
}

// workflowRunCmd represents the workflow run command
var workflowRunCmd = &cobra.Command{
	Use:   "run",
	Short: "Run a workflow",
	Long:  `Run a DataFlare workflow.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Create client
		c := client.NewClient(
			apiURL,
			client.WithAPIKey(apiKey),
			client.WithTimeout(30*time.Second),
		)

		// Parse parameters
		var params map[string]interface{}
		if parameters != "" {
			if err := json.Unmarshal([]byte(parameters), &params); err != nil {
				return err
			}
		}

		// Run workflow
		run, err := c.RunWorkflow(context.Background(), workflowID, &client.RunWorkflowRequest{
			Parameters: params,
		})
		if err != nil {
			return err
		}

		// Wait for workflow to complete
		if wait {
			fmt.Printf("Waiting for workflow run %s to complete...\n", run.ID)
			ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
			defer cancel()

			for {
				// Get workflow run
				run, err = c.GetWorkflowRun(ctx, workflowID, run.ID)
				if err != nil {
					return err
				}

				// Check if workflow is completed
				if run.Status == "completed" || run.Status == "failed" || run.Status == "cancelled" {
					break
				}

				// Check if context is done
				select {
				case <-ctx.Done():
					return fmt.Errorf("timeout waiting for workflow run to complete")
				default:
					// Wait for a while
					time.Sleep(time.Duration(pollInterval) * time.Second)
				}
			}
		}

		// Output
		switch outputFormat {
		case "json":
			json, err := json.MarshalIndent(run, "", "  ")
			if err != nil {
				return err
			}
			fmt.Println(string(json))
		case "yaml":
			yaml, err := yaml.Marshal(run)
			if err != nil {
				return err
			}
			fmt.Println(string(yaml))
		default:
			fmt.Println("WORKFLOW RUN")
			fmt.Printf("ID: %s\n", run.ID)
			fmt.Printf("Workflow ID: %s\n", run.WorkflowID)
			fmt.Printf("Status: %s\n", run.Status)
			fmt.Printf("Start time: %s\n", run.StartTime.Format(time.RFC3339))
			if !run.EndTime.IsZero() {
				fmt.Printf("End time: %s\n", run.EndTime.Format(time.RFC3339))
			}
			if run.Error != "" {
				fmt.Printf("Error: %s\n", run.Error)
			}
		}

		return nil
	},
}

// workflowRunListCmd represents the workflow run list command
var workflowRunListCmd = &cobra.Command{
	Use:   "list-runs",
	Short: "List workflow runs",
	Long:  `List DataFlare workflow runs.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Create client
		c := client.NewClient(
			apiURL,
			client.WithAPIKey(apiKey),
			client.WithTimeout(30*time.Second),
		)

		// List workflow runs
		response, err := c.ListWorkflowRuns(context.Background(), workflowID, nextToken, limit)
		if err != nil {
			return err
		}

		// Output
		switch outputFormat {
		case "json":
			json, err := json.MarshalIndent(response, "", "  ")
			if err != nil {
				return err
			}
			fmt.Println(string(json))
		case "yaml":
			yaml, err := yaml.Marshal(response)
			if err != nil {
				return err
			}
			fmt.Println(string(yaml))
		default:
			fmt.Println("WORKFLOW RUNS")
			fmt.Println("ID\tWORKFLOW ID\tSTATUS\tSTART TIME\tEND TIME")
			for _, run := range response.Runs {
				endTime := ""
				if !run.EndTime.IsZero() {
					endTime = run.EndTime.Format(time.RFC3339)
				}
				fmt.Printf("%s\t%s\t%s\t%s\t%s\n",
					run.ID,
					run.WorkflowID,
					run.Status,
					run.StartTime.Format(time.RFC3339),
					endTime,
				)
			}
			if response.NextToken != "" {
				fmt.Printf("\nNext token: %s\n", response.NextToken)
			}
		}

		return nil
	},
}

// workflowRunGetCmd represents the workflow run get command
var workflowRunGetCmd = &cobra.Command{
	Use:   "get-run",
	Short: "Get a workflow run",
	Long:  `Get a DataFlare workflow run.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Create client
		c := client.NewClient(
			apiURL,
			client.WithAPIKey(apiKey),
			client.WithTimeout(30*time.Second),
		)

		// Get workflow run
		run, err := c.GetWorkflowRun(context.Background(), workflowID, runID)
		if err != nil {
			return err
		}

		// Output
		switch outputFormat {
		case "json":
			json, err := json.MarshalIndent(run, "", "  ")
			if err != nil {
				return err
			}
			fmt.Println(string(json))
		case "yaml":
			yaml, err := yaml.Marshal(run)
			if err != nil {
				return err
			}
			fmt.Println(string(yaml))
		default:
			fmt.Println("WORKFLOW RUN")
			fmt.Printf("ID: %s\n", run.ID)
			fmt.Printf("Workflow ID: %s\n", run.WorkflowID)
			fmt.Printf("Status: %s\n", run.Status)
			fmt.Printf("Start time: %s\n", run.StartTime.Format(time.RFC3339))
			if !run.EndTime.IsZero() {
				fmt.Printf("End time: %s\n", run.EndTime.Format(time.RFC3339))
			}
			if run.Error != "" {
				fmt.Printf("Error: %s\n", run.Error)
			}
		}

		return nil
	},
}

// workflowRunCancelCmd represents the workflow run cancel command
var workflowRunCancelCmd = &cobra.Command{
	Use:   "cancel-run",
	Short: "Cancel a workflow run",
	Long:  `Cancel a DataFlare workflow run.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		// Create client
		c := client.NewClient(
			apiURL,
			client.WithAPIKey(apiKey),
			client.WithTimeout(30*time.Second),
		)

		// Cancel workflow run
		err := c.CancelWorkflowRun(context.Background(), workflowID, runID)
		if err != nil {
			return err
		}

		fmt.Printf("Workflow run %s cancelled\n", runID)

		return nil
	},
}

func init() {
	rootCmd.AddCommand(workflowCmd)

	// Add subcommands
	workflowCmd.AddCommand(workflowListCmd)
	workflowCmd.AddCommand(workflowCreateCmd)
	workflowCmd.AddCommand(workflowGetCmd)
	workflowCmd.AddCommand(workflowUpdateCmd)
	workflowCmd.AddCommand(workflowDeleteCmd)
	workflowCmd.AddCommand(workflowRunCmd)
	workflowCmd.AddCommand(workflowRunListCmd)
	workflowCmd.AddCommand(workflowRunGetCmd)
	workflowCmd.AddCommand(workflowRunCancelCmd)

	// List command flags
	workflowListCmd.Flags().IntVar(&limit, "limit", 10, "Maximum number of workflows to return")
	workflowListCmd.Flags().StringVar(&nextToken, "next-token", "", "Next token for pagination")

	// Create command flags
	workflowCreateCmd.Flags().StringVar(&workflowName, "name", "", "Workflow name")
	workflowCreateCmd.Flags().StringVar(&workflowDescription, "description", "", "Workflow description")
	workflowCreateCmd.Flags().StringVar(&workflowFile, "file", "", "Workflow definition file")
	workflowCreateCmd.MarkFlagRequired("name")
	workflowCreateCmd.MarkFlagRequired("file")

	// Get command flags
	workflowGetCmd.Flags().StringVar(&workflowID, "id", "", "Workflow ID")
	workflowGetCmd.MarkFlagRequired("id")

	// Update command flags
	workflowUpdateCmd.Flags().StringVar(&workflowID, "id", "", "Workflow ID")
	workflowUpdateCmd.Flags().StringVar(&workflowDescription, "description", "", "Workflow description")
	workflowUpdateCmd.Flags().StringVar(&workflowFile, "file", "", "Workflow definition file")
	workflowUpdateCmd.MarkFlagRequired("id")

	// Delete command flags
	workflowDeleteCmd.Flags().StringVar(&workflowID, "id", "", "Workflow ID")
	workflowDeleteCmd.MarkFlagRequired("id")

	// Run command flags
	workflowRunCmd.Flags().StringVar(&workflowID, "id", "", "Workflow ID")
	workflowRunCmd.Flags().StringVar(&parameters, "parameters", "", "Workflow parameters (JSON)")
	workflowRunCmd.Flags().BoolVar(&wait, "wait", false, "Wait for workflow to complete")
	workflowRunCmd.Flags().IntVar(&timeout, "timeout", 60, "Timeout in seconds")
	workflowRunCmd.Flags().IntVar(&pollInterval, "poll-interval", 1, "Poll interval in seconds")
	workflowRunCmd.MarkFlagRequired("id")

	// Run list command flags
	workflowRunListCmd.Flags().StringVar(&workflowID, "id", "", "Workflow ID")
	workflowRunListCmd.Flags().IntVar(&limit, "limit", 10, "Maximum number of runs to return")
	workflowRunListCmd.Flags().StringVar(&nextToken, "next-token", "", "Next token for pagination")
	workflowRunListCmd.MarkFlagRequired("id")

	// Run get command flags
	workflowRunGetCmd.Flags().StringVar(&workflowID, "workflow-id", "", "Workflow ID")
	workflowRunGetCmd.Flags().StringVar(&runID, "run-id", "", "Run ID")
	workflowRunGetCmd.MarkFlagRequired("workflow-id")
	workflowRunGetCmd.MarkFlagRequired("run-id")

	// Run cancel command flags
	workflowRunCancelCmd.Flags().StringVar(&workflowID, "workflow-id", "", "Workflow ID")
	workflowRunCancelCmd.Flags().StringVar(&runID, "run-id", "", "Run ID")
	workflowRunCancelCmd.MarkFlagRequired("workflow-id")
	workflowRunCancelCmd.MarkFlagRequired("run-id")
}
